1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 *
19 */
20 package org.apache.mina.util;
21
22 import org.slf4j.MDC;
23 import org.slf4j.helpers.BasicMDCAdapter;
24
25 import java.util.logging.Formatter;
26 import java.util.logging.LogRecord;
27 import java.util.Set;
28 import java.util.Arrays;
29
30 /**
31 * Implementation of {@link java.util.logging.Formatter} that generates xml in the log4j format.
32 * <p>
33 * The generated xml corresponds 100% with what is generated by
34 * log4j's <a href=http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/XMLLayout.html">XMLLayout</a>
35 * <p>
36 * The MDC properties will only be correct when <code>format</code> is called from the same thread
37 * that generated the LogRecord.
38 * <p>
39 * The file and line attributes in the locationInfo element will always be "?"
40 * since java.util.logging.LogRecord does not provide that info.
41 * <p>
42 * The implementation is heavily based on org.apache.log4j.xml.XMLLayout
43 * </p>
44 *
45 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
46 */
47 public class Log4jXmlFormatter extends Formatter {
48
49 private final int DEFAULT_SIZE = 256;
50 private final int UPPER_LIMIT = 2048;
51
52 private StringBuffer buf = new StringBuffer(DEFAULT_SIZE);
53 private boolean locationInfo = false;
54 private boolean properties = false;
55
56 /**
57 * The <b>LocationInfo</b> option takes a boolean value. By default,
58 * it is set to false which means there will be no location
59 * information output by this layout. If the the option is set to
60 * true, then the file name and line number of the statement at the
61 * origin of the log statement will be output.
62 *
63 * @param flag whether locationInfo should be output by this layout
64 */
65 public void setLocationInfo(boolean flag) {
66 locationInfo = flag;
67 }
68
69 /**
70 * Returns the current value of the <b>LocationInfo</b> option.
71 *
72 * @return whether locationInfo will be output by this layout
73 */
74 public boolean getLocationInfo() {
75 return locationInfo;
76 }
77
78 /**
79 * Sets whether MDC key-value pairs should be output, default false.
80 *
81 * @param flag new value.
82 */
83 public void setProperties(final boolean flag) {
84 properties = flag;
85 }
86
87 /**
88 * Gets whether MDC key-value pairs should be output.
89 *
90 * @return true if MDC key-value pairs are output.
91 */
92 public boolean getProperties() {
93 return properties;
94 }
95
96 @SuppressWarnings("unchecked")
97 public String format(final LogRecord record) {
98 // Reset working buffer. If the buffer is too large, then we need a new
99 // one in order to avoid the penalty of creating a large array.
100 if (buf.capacity() > UPPER_LIMIT) {
101 buf = new StringBuffer(DEFAULT_SIZE);
102 } else {
103 buf.setLength(0);
104 }
105 buf.append("<log4j:event logger=\"");
106 buf.append(Transform.escapeTags(record.getLoggerName()));
107 buf.append("\" timestamp=\"");
108 buf.append(record.getMillis());
109 buf.append("\" level=\"");
110
111 buf.append(Transform.escapeTags(record.getLevel().getName()));
112 buf.append("\" thread=\"");
113 buf.append(String.valueOf(record.getThreadID()));
114 buf.append("\">\r\n");
115
116 buf.append("<log4j:message><![CDATA[");
117 // Append the rendered message. Also make sure to escape any
118 // existing CDATA sections.
119 Transform.appendEscapingCDATA(buf, record.getMessage());
120 buf.append("]]></log4j:message>\r\n");
121
122 if (record.getThrown() != null) {
123 String[] s = Transform.getThrowableStrRep(record.getThrown());
124 if (s != null) {
125 buf.append("<log4j:throwable><![CDATA[");
126 for (String value : s) {
127 Transform.appendEscapingCDATA(buf, value);
128 buf.append("\r\n");
129 }
130 buf.append("]]></log4j:throwable>\r\n");
131 }
132 }
133
134 if (locationInfo) {
135 buf.append("<log4j:locationInfo class=\"");
136 buf.append(Transform.escapeTags(record.getSourceClassName()));
137 buf.append("\" method=\"");
138 buf.append(Transform.escapeTags(record.getSourceMethodName()));
139 buf.append("\" file=\"?\" line=\"?\"/>\r\n");
140 }
141
142 if (properties) {
143 if (MDC.getMDCAdapter() instanceof BasicMDCAdapter) {
144 BasicMDCAdapter mdcAdapter = (BasicMDCAdapter) MDC.getMDCAdapter();
145 Set keySet = mdcAdapter.getKeys();
146 if (keySet != null && keySet.size() > 0) {
147 buf.append("<log4j:properties>\r\n");
148 Object[] keys = keySet.toArray();
149 Arrays.sort(keys);
150 for (Object key1 : keys) {
151 String key = key1.toString();
152 Object val = mdcAdapter.get(key);
153 if (val != null) {
154 buf.append("<log4j:data name=\"");
155 buf.append(Transform.escapeTags(key));
156 buf.append("\" value=\"");
157 buf.append(Transform.escapeTags(String.valueOf(val)));
158 buf.append("\"/>\r\n");
159 }
160 }
161 buf.append("</log4j:properties>\r\n");
162 }
163 }
164 }
165 buf.append("</log4j:event>\r\n\r\n");
166
167 return buf.toString();
168 }
169
170 }